home *** CD-ROM | disk | FTP | other *** search
- /* NCRRunScript.c */
- /*
- * NCRRunScript.c
- * Copyright © 1994 Apple Computer Inc. All rights reserved.
- */
- /* .___________________________________________________________________________________.
- | These low-level routines access the NCR 53C825 chip registers. They start an |
- | asynchrous operation (either a general SCSI I/O request or a Bus Reset operation) |
- | and follow its operation to the bitter end. While the logic is extremely specific |
- | to the NCR chip, you should at least read through this file to understand the |
- | relation between the hardware-specific code and the Driver Services code. |
- | |
- | Almost everything here is specific to the NCR chip. The watchdog timeout will |
- | be generally useful. However, it has not been tested yet. |
- .___________________________________________________________________________________.
- */
-
- #include "NCRDriverPrivate.h"
- /*
- * stddef defines offsetof()
- */
- #include <stddef.h>
- /*
- * WARNING: the bus reset script hangs if you turn on single-stepping. I suspect that
- * I'm not handling multiple interrupt conditions correctly. I "fixed" this by using
- * the DriverServices DelayForHardware routine to do bus reset delays. However, in
- * general, you should not expect single-step to work correctly.
- */
- #define SINGLE_STEP 0 /* Used for debugging the script */
- #ifndef SINGLE_STEP_DEFAULT
- #define SINGLE_STEP_DEFAULT 0
- #endif
-
- void StartWatchdogTimeout(
- register PerRequestDataPtr perRequestDataPtr
- );
- OSStatus WatchdogTimerCompletion(
- void *p1,
- void *p2
- );
- void StartScript(
- register PerRequestDataPtr perRequestDataPtr,
- UInt32 scriptPtr
- );
-
- #define REQUEST (*perRequestDataPtr)
- #define SCRIPT (REQUEST.scriptData)
- #define SHADOW (REQUEST.shadow)
-
- void StoreDMAParameters(
- PerRequestDataPtr perRequestDataPtr
- );
-
- #if USE_LOG_LIBRARY
- void LogScriptInterrupt(
- register PerRequestDataPtr perRequestDataPtr
- );
- void DumpRegisters(
- register PerRequestDataPtr perRequestDataPtr
- );
- #else
- #define LogScriptInterrupt(per) /* Nothing */
- #define DumpRegisters(perRequestDataPtr) /* Nothing */
- #endif
-
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * This is the primary interrupt service routine that is called by the system. Input
- * parameters are ignored.
- *
- * This subroutine handles the interrupt from the NCR chip. The logic is from the
- * NCR Family Programmers Guide, page 8-30 ff.
- *
- * Note that we must handle phase-mismatch errors here. In particular, we must handle
- * residual byte count problems (Request Sense with a larger buffer than the
- * device plans to deliver) as they are not fatal errors.
- */
- InterruptMemberNumber
- PCIInterruptServiceRoutine(
- InterruptSetMember member,
- void *refCon,
- UInt32 theIntCount
- )
- {
- OSErr status;
- register PerRequestDataPtr perRequestDataPtr;
- unsigned short oldPhase;
- InterruptMemberNumber result;
- IOParam *pb;
- NCRSCSIParamPtr scsiParamPtr;
- short stallLoop;
- static const Nanoseconds gTenMsec = { 0, 10000000 };
- #define PB (*pb)
- #define SCSI (*scsiParamPtr)
-
- Trace(PCIInterruptServiceRoutine);
- UNUSED(member);
- UNUSED(refCon);
- UNUSED(theIntCount);
- /*
- * We should try to establish which "per-request" interrupted the processor.
- * Currently, we support only a single transfer, but this routine will have to
- * get more complex (by querying the NCR chip) when we support suspend-resume
- * or tagged commands. For example, we could use the data storage address.
- * The actual interrupt is processed by a primary interrupt routine. When the
- * I/O operation is completed, it queues a secondary interrupt routine that
- * frees resources and calls IOCommandIsComplete.
- */
- perRequestDataPtr = GLOBAL.perRequestDataPtr;
- /*
- * These are only needed for re-preparation.
- */
- pb = (IOParam *) REQUEST.pb; /* pb can be NULL */
- scsiParamPtr = (pb == NULL) ? NULL : (NCRSCSIParamPtr) PB.ioMisc;
- status = kIOBusyStatus; /* Changed by completed I/O */
- SHADOW.istat = ReadByte(ISTAT); /* 0x14 Interrupt Status */
- /*
- * We must check that at least one interrupt bit is set -- if none are set,
- * this is a spurious interrupt -- we then return kIsrIsNotComplete
- */
- result = ((SHADOW.istat & (bit2 | bit1 | bit0)) != 0)
- ? kIsrIsComplete : kIsrIsNotComplete;
- if ((SHADOW.istat & bit2) != 0) { /* INTF: Interrupt on the fly? */
- /*
- * This bit must be written to 1 to clear it. This interrupt must be
- * handled before handling other interrupt conditions. We don't
- * currently use the INTF interrupt.
- */
- WriteByte(ISTAT, SHADOW.istat | bit2);
- SynchronizeIO(); /* eieio */
- SHADOW.istat = ReadByte(ISTAT); /* 0x14 Int. Status */
- }
- if ((SHADOW.istat & (bit1 | bit0)) != 0) {
- /*
- * If the SIP and DIP interrupts are clear, this is a "true" interrupt on
- * the fly. Exit the interrupt service routine to let the script continue
- * in peace -- we're not allowed to touch other NCR chip registers.
- * If either bit is set, this is a normal interrupt. Save the interesting
- * registers in the per-request block: this is for debugging convenience.
- *
- * Note: we might need to fix this algorithm. See the discussion in the
- * NCR 53C825 manual for the reason behind clearing ISTAT.
- */
- #if 0
- if ((SHADOW.istat & (bit1 | bit0)) == bit0)
- WriteByte(ISTAT, 0);
- #endif
- SHADOW.dbc = ReadLong(DBC); /* 0x24 byte count */
- SHADOW.dsp = ReadLong(DSP); /* 0x2C Script pointer */
- SHADOW.scriptPCOffset = SHADOW.dsp - REQUEST.scriptBaseAddress - 8;
- #if 1 && USE_LOG_LIBRARY
- WriteLogEntry(GLOBAL.logRecordPtr, 'PISR',
- LogFormat4(kLogFormatAddress, kLogFormatAddress, kLogFormatSigned, kLogFormatString),
- (UInt32) SHADOW.dsp,
- (UInt32) REQUEST.scriptBaseAddress,
- (SInt32) SHADOW.scriptPCOffset,
- "\pScript @ isr"
- );
- #endif
- SHADOW.dsps = ReadLong(DSPS); /* 0x30 Script 2nd long */
- /*
- * Read sist0 and sist1 before reading dstat. The calls to
- * DelayForHardware enforce a 12 clock tick delay between reads.
- */
- SHADOW.sist0 = ReadByte(SIST0); /* 0x42 SCSI Int. Sts 0 */
- DelayForHardware(GLOBAL.clockTick12);
- SHADOW.sist1 = ReadByte(SIST1); /* 0x43 SCSI Int. Sts 1 */
- DelayForHardware(GLOBAL.clockTick12);
- SHADOW.dstat = ReadByte(DSTAT); /* 0x0C DMA status */
- SHADOW.scntl1 = ReadByte(SCNTL1); /* 0x01 SCSI control 1 */
- SHADOW.socl = ReadByte(SOCL); /* 0x09 SCSI output ctl */
- SHADOW.sbcl = ReadByte(SBCL); /* 0x0B Bus ctl signals */
- SHADOW.sstat0 = ReadByte(SSTAT0); /* 0x0D core status */
- SHADOW.sstat1 = ReadByte(SSTAT1); /* 0x0E core status */
- SHADOW.sstat2 = ReadByte(SSTAT2); /* 0x0F core status */
- SHADOW.dfifo = ReadByte(DFIFO); /* 0x20 fifo count */
- SHADOW.dbc &= 0x00FFFFFF; /* Only 24 bits */
- LogScriptInterrupt(perRequestDataPtr); /* Debug: log registers */
- if ((SHADOW.istat & bit0) != 0) { /* DMA interrupt pending? */
- if ((SHADOW.dstat & bit7) == 0) { /* DMA fifo not empty? */
- LogHex(SHADOW.dstat, "\pDMA fifo not empty");
- /*
- * Clear the dma fifo non-empty condition by resetting the dma
- * and scsi fifos. Hmm, how does this deal with partial (short)
- * preparation?
- */
- SHADOW.stest3 = ReadByte(STEST3);
- WriteByte(STEST3, SHADOW.stest3 | bit1);
- SynchronizeIO(); /* eieio */
- SHADOW.ctest3 = ReadByte(CTEST3);
- WriteByte(CTEST3, SHADOW.ctest3 | bit2);
- }
- if ((SHADOW.dstat & (bit5 | bit6)) != 0) { /* PCI bus fault? */
- LogHex(SHADOW.dstat, "\pPCIBusFault");
- LogHex(ReadLong(DBC), "\pDBC 0x24-26, DNAD 0x27 - count, cmd");
- LogHex(ReadLong(DNAD), "\pDNAD 0x28 - DMA next address");
- LogHex(ReadLong(DSPS), "\pDSPS 0x30 - Scripts pointer save");
- status = scsiTerminated;
- }
- else if ((SHADOW.dstat & bit0) != 0) { /* Illegal Script opcode? */
- LogHex(SHADOW.scriptPCOffset, "\pIllegal Script @ pc offset");
- DumpRegisters(perRequestDataPtr);
- status = ioErr;
- }
- else if ((SHADOW.dstat & bit4) != 0) { /* Abort error (IOKill) */
- /*
- * Note: we'll get one of these if the non-interrupt variant
- * times out. A better error message mechanism is probably needed.
- * A KillIO request also causes one of these.
- */
- LogString("\pChip-detected abort");
- WriteByte(ISTAT, 0); /* Clear ISTAT again */
- /*
- * To do: look at the current bus status: if we are connected
- * and REQ is set, the target is trying to do something and we
- * aren't responding. We should restart the script at the
- * rundown address. If we are connected and the target is
- * not in REQ, the device is, how should we put it, dead. About
- * all we can do is exit the script and hope that the caller
- * tosses a Bus Reset or I/O Rundown Control call at us.
- */
- status = scsiCommandTimeout;
- }
- else if ((SHADOW.dstat & bit2) != 0) { /* Script INT instruction */
- //** LogDecimal(SHADOW.dsps, "\pScript Interrupt");
- WriteByte(STIME0, 0); /* Cancel timers */
- WriteByte(STIME1, 0); /* Cancel both of them */
- REQUEST.dmaFirstPrepared += REQUEST.dmaLengthPrepared;
- status = SHADOW.dsps;
- if (status == kIntNeedAnotherPreparation) {
- if (scsiParamPtr != NULL
- && SCSI.driverAction != kNCRDriverNoDataPhase) {
- /*
- * We are in a data phase, but don't have any prepared I/O.
- * Prepare the next chunk of DMA and restart the script.
- * If PrepareNextDMA returns scsiDataRunError, queue the
- * Secondary Interrupt Handler to schedule our Software
- * Task that will call PrepareMemoryForIO, setup the next
- * chunk of the transfer, and then queue the Secondary
- * Interrupt routine again to restart the device.
- */
- #if 0
- LogDecimal(
- (SInt32) ReadByte(CTEST0),
- "\pCalling Prepare[Next Area]"
- );
- #endif
- status = PrepareNextDMA(perRequestDataPtr);
- if (status == scsiDataRunError)
- status = kPrepareMemoryStartTask;
- else {
- StoreDMAParameters(perRequestDataPtr);
- WriteByte(CTEST0, SCSI.driverAction);
- status = kIOBusyStatus;
- }
- }
- else {
- status = scsiTransferTypeInvalid; /* Oops */
- }
- }
- else {
- /*
- * This is some other "final" SCSI interrupt status.
- */
- }
- }
- if ((SHADOW.dstat & bit3) != 0) {
- /*
- * Single-step interrupt. We have presumably logged the interrupt
- * so we need only restart the process. This may be insufficient:
- * we might need to re-read the registers to see if other interrupt
- * conditions need to be cleared.
- *
- * Warning: the single-step stuff doesn't work correctly as the
- * chip apparently gives us multiple interrupt conditions which
- * the interrupt service routine doesn't handle properly.
- */
- }
- } /* If DMA interrupt or script complete */
- if ((SHADOW.istat & bit1) != 0) { /* SIP: SCSI interrupt pending? */
- /*
- * Process a SCSI interrupt.
- */
- if ((SHADOW.sist1 & bit2) != 0) { /* Selection timeout? */
- WriteByte(STIME0, 0); /* Clear the timer */
- status = scsiSelectTimeout;
- }
- else if ((SHADOW.sist1 & bit1) != 0) { /* General timeout? */
- WriteByte(STIME1, 0); /* Clear the timer */
- }
- else if ((SHADOW.sist0 & (bit3 | bit2 | bit1 | bit0)) == bit1) {
- /*
- * Bus Reset. If SCSI Control 1 has RST (bit 3) set, indicating
- * that we asserted bus Reset, generate a private status that
- * the secondary interrupt routine will use to stall the script
- * for 250 msec. Then, it restarts the script to clear the
- * bus reset condition.
- */
- if ((SHADOW.scntl1 & bit3) != 0) {
- status = kBusResetRestart; /* Private status */
- }
- else {
- status = scsiSCSIBusReset;
- LogString("\pUnexpected bus reset");
- }
- }
- else if ((SHADOW.sist0 & (bit3 | bit2 | bit1 | bit0)) != 0) {
- /*
- * bit3: SCSI gross error (data under/overflow)
- * bit2: Unexpected disconnect
- * bit1: Bus Reset (and some other SCSI interrupt)
- * bit0: Bus parity error.
- */
- if (REQUEST.scriptSelector == kSCSICommandScript) {
- switch (SHADOW.sist0 & (bit3 | bit2 | bit1 | bit0)) {
- case bit0: status = scsiParityError; break;
- case bit1: status = scsiSCSIBusReset; break;
- case bit2: status = scsiUnexpectedBusFree; break;
- case bit3: status = scsiDataRunError; break;
- default: status = scsiTerminated; break;
- }
- }
- if (status == scsiSCSIBusReset /* Bus reset? */
- && REQUEST.scriptSelector == kBusResetScript) /* Ignore ours */
- status = noErr;
- else {
- LogHex(SHADOW.sist0, "\pStrange SCSI error");
- }
- }
- else if ((SHADOW.sist0 & bit7) != 0) { /* Phase mismatch? */
- /*
- * Phase mismatch - this is often an error, but we do allow
- * short transfers, where the user requested a longer buffer
- * than the target intends to provide. We check this by
- * (1) checking for "old" phase in SHADOW.socl. Look for DATI.
- * (2) checking for "new" phase -- it should be STATUS, but this
- * is only for debugging: the script will eventually fail if
- * this is not the case.
- * (3) checking the DMA fifo -- it should be empty
- * (4) checking the DBC transfer count -- it should be nonzero.
- * Other cases are errors. The script continues after phase-
- * mismatch errors: it will eventually finish with a bus-free
- * status and an Int instruction.
- *
- * To handle partial preparationn, scatter-gather, or other
- * non-contiguous physical memory situations, we queue a secondary
- * interrupt handler that recovers the residual dma count (the
- * following code will be copied there), it then calls
- * PrepareMemoryForIO to update the physical mapping pointers,
- * updates the data table, and restarts the operation. Too much
- * work for a sample.
- */
- oldPhase = SHADOW.socl & 0x07;
- if (oldPhase == DATO || oldPhase == DATI) {
- /*
- * Try to recover the residual dma count. The algorithm is
- * from page 5-26 of the NCR data manual:
- * 1. Subtract the seven least significant bits of the dbc
- * register from the 7-bit value of the dfifo register.
- * 2. And the result with 0x7F for a byte count between
- * zero and 64.
- */
- SHADOW.residualTransferCount = SHADOW.dbc
- + (((SHADOW.dfifo & 0x7F) - (SHADOW.dbc & 0x7F)) & 0x7F);
- /*
- * Byte count will be a value between zero and 64.
- * If this was a send (DATO, MSGO, CMD) operation, look
- * at the SSTAT0 and SSTAT2 registers to see if bytes remain
- * in the SODL register. This probably needs work.
- */
- if (oldPhase == DATO) {
- if ((SHADOW.sstat0 & bit5) != 0)
- ++SHADOW.residualTransferCount;
- if ((SHADOW.sstat2 & bit5) != 0)
- ++SHADOW.residualTransferCount;
- }
- else {
- if ((SHADOW.sstat0 & bit7) != 0)
- ++SHADOW.residualTransferCount;
- if ((SHADOW.sstat2 & bit7) != 0)
- ++SHADOW.residualTransferCount;
- }
- if (pb != NULL)
- PB.ioActCount -= SHADOW.residualTransferCount;
- } /* If DATI or DATO phase */
- } /* If this is a phase mismatch error */
- }
- }
- if (status != kIOBusyStatus) {
- /*
- * Turn off the interrupts and chip timers to prevent an unexpected
- * interrupt: our global per-request record risks reentrancy bugs.
- * If we restart I/O we will re-enable timers and interrupts.
- */
- WriteByte(DIEN, 0);
- WriteByte(SIEN0, 0);
- WriteByte(SIEN1, 0);
- WriteByte(STIME0, 0);
- WriteByte(STIME1, 0);
- /*
- * Note that actual completion is done from a secondary interrupt
- * handler as we must Checkpoint the I/O buffers.
- */
- for (stallLoop = 0; stallLoop < 100; stallLoop++) {
- status = NCRQueueSecondaryInterrupt(perRequestDataPtr, status);
- if (status == noErr)
- break;
- /*
- * For some reason, we couldn't queue a secondary interrupt handler
- * Try after a brief stall. This probably won't work and certainly
- * won't be tested adaquately.
- */
- LogStatusString(status, "\pQueueSecondaryInterruptHandler fail");
- DelayForHardware(gTenMsec);
- }
- }
- else {
- StartScript(perRequestDataPtr, SHADOW.dsp);
- }
- /*
- * Jump directly to exit to leave the interrupt service routine without
- * restarting the script or scheduling a secondary interrupt routine.
- */
- exit: return (result);
- #undef PB
- #undef SCSI
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * NCRQueueSecondaryInterrupt
- *
- * This routine queues the secondary interrupt handler. It was centralized to help
- * debug the system ROMs.
- */
- OSErr
- NCRQueueSecondaryInterrupt(
- PerRequestDataPtr perRequestDataPtr,
- OSErr statusOrSignal
- )
- {
- OSStatus osStatus;
-
- Trace(NCRQueueSecondaryInterrupt);
- LogStatusString(statusOrSignal, "\pAt NCRQueueSecondaryInterrupt");
- osStatus = QueueSecondaryInterruptHandler(
- NCRSecondaryInterruptHandler,
- NULL,
- (void *) perRequestDataPtr,
- (void *) statusOrSignal
- );
- CheckStatus(osStatus, "\pQueueSecondaryInterruptHandler2");
- LogStatusString(statusOrSignal, "\pQueueSecondaryInterruptHandler returns");
- return (osStatus);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * NCRSecondaryInterruptHandler
- *
- * This is a secondary interrupt handler that is called by the primary interrupt
- * service routine to complete an asynchronous I/O request. It is also called
- * directly by the KillIO handler. It uses the atomic test-and-set operation to
- * ensure that each request is completed exactly once.
- *
- * This is also called to restart I/O from the Software Interrupt Task
- */
- OSStatus
- NCRSecondaryInterruptHandler(
- void *p1,
- void *p2
- )
- {
- register PerRequestDataPtr perRequestDataPtr;
- OSErr ioResult;
- /*
- * Note that status is used here only for adminstrative status from the
- * Driver Services Library calls -- it is never passed back to the caller.
- */
- OSErr status;
- OSStatus osStatus;
- ParmBlkPtr pb;
- NCRSCSIParamPtr scsiParamPtr;
- UInt32 scriptPtr;
- #define IOPB (pb->ioParam)
- #define SCSI (*scsiParamPtr)
-
- LogStatusString((UInt32) p2, "\pIn Secondary Handler");
- Trace(NCRSecondaryInterruptHandler);
- perRequestDataPtr = (PerRequestDataPtr) p1;
- pb = REQUEST.pb;
- ioResult = (OSErr) p2;
- /*
- * There are a couple of private ioResult values that are used for
- * intermediate script operations.
- */
- switch (ioResult) {
- case kIORequestStart:
- SHADOW.residualTransferCount = 0;
- switch (REQUEST.scriptSelector) {
- case kSCSICommandScript:
- LogString("\pStart SCSI I/O");
- WriteByte(SCID, GLOBAL.initiatorID);
- StoreDMAParameters(perRequestDataPtr);
- /*
- * Timeout if selection fails after 409.6 msec. This is the smallest
- * value larger than the SCSI Standard 250 msec. timeout value.
- */
- WriteByte(STIME0, 13); /* Selection timer */
- WriteLong(DSA, REQUEST.scriptDataPtr);
- SynchronizeIO(); /* eieio */
- break;
- case kBusResetScript:
- LogString("\pStart Bus Reset");
- /*
- * No initiator ID, selection timeout or DSA.
- */
- WriteByte(SCID, GLOBAL.initiatorID);
- WriteByte(STIME0, 0);
- WriteLong(DSA, kInvalidPageAddress);
- break;
- case kSCSITestISRScript:
- case kSCSITestMemoryScript:
- StoreDMAParameters(perRequestDataPtr);
-
- LogMemory(&REQUEST.memoryMoveScript[0], sizeof REQUEST.memoryMoveScript);
- LogMemory((void *) REQUEST.scriptPtr, sizeof REQUEST.memoryMoveScript);
-
- WriteLong(DSA, kInvalidPageAddress);
- break;
- }
- StartWatchdogTimeout(perRequestDataPtr);
- StartScript(perRequestDataPtr, REQUEST.scriptPtr);
- break;
- case kPrepareMemoryStartTask: /* Primary Interrupt needs PrepareMemory */
- /*
- * We've run off the end of the preparation. Queue a Software Interrupt
- * that will call PrepareMemoryForIO to handle the partial preparation.
- * If this fails, restart I/O to rundown the SCSI device. Because this
- * will run "task level" and other I/O may intervene, we may want to
- * cancel the timer (for now) and restart it when the driver resumes
- * operation. This requires thought, but I don't know the right answer;
- * but I'll try it for now.
- *
- * Hmm, to do this right, CancelWatchdogTimer ought to return "time
- * remaining" which will be used to restart the timer, rather than
- * always starting at the original timeout.
- */
- needAnotherPrepareMemory:
- CancelWatchdogTimer(perRequestDataPtr);
- osStatus = SendSoftwareInterrupt(REQUEST.nextDMAInterruptID, 0);
- CheckStatus(osStatus, "\pSendSoftwareInterrupt");
- if (osStatus != noErr) {
- /*
- * We couldn't queue a software interrupt. Force the
- * device into its failure script. The device will, eventually,
- * exit and return an error ioResult to the caller.
- */
- WriteByte(CTEST0, 0); /* This will cause rundown */
- scriptPtr = ((UInt32) GLOBAL.scriptIOTable.physicalMapping[0])
- + kSCSIRundownScript;
- StartScript(perRequestDataPtr, scriptPtr);
- }
- break;
- case kPrepareMemoryRestart: /* PrepareMemoryForIO called */
- LogString("\pPrepMemory restart");
- StoreDMAParameters(perRequestDataPtr);
- StartWatchdogTimeout(perRequestDataPtr);
- if (REQUEST.scriptSelector == kSCSITestMemoryScript) {
- scriptPtr = ((UInt32) REQUEST.perRequestIOTable.physicalMapping[0])
- + offsetof(PerRequestData, memoryMoveScript);
- }
- else {
- scriptPtr = ((UInt32) GLOBAL.scriptIOTable.physicalMapping[0])
- + kSCSIRestartScript;
- }
- StartScript(perRequestDataPtr, scriptPtr);
- break;
- case kBusResetRestart: /* Bus reset: stall and restart the script */
- LogString("\pBus Reset restart");
- DelayForHardware(GLOBAL.msec250);
- scriptPtr = ((UInt32) GLOBAL.scriptIOTable.physicalMapping[0])
- + kBusResetScriptRestart;
- StartScript(perRequestDataPtr, scriptPtr);
- break;
- /*
- * These are intermediate error values: restart at 'Fail'
- */
- case kIntFailStrangePhase: /* Bug: unknown phase at phase loop */
- case kIntDataPhaseExpected: /* At 'Data', but not in data phase */
- case kIntPreparationFailed: /* CTEST == 0 after prep restart */
- case kIntDataOutNoData: /* CTEST not == 2 at DATO phase */
- case kIntDataInNoData: /* CTEST not == 1 at DATI phase */
- case kIntNotMsgInAfterStatus: /* STS phase must be followed by MSGI */
- LogStatusString(ioResult, "\pIntermediate Script Error");
- scriptPtr = ((UInt32) GLOBAL.scriptIOTable.physicalMapping[0])
- + kSCSIRundownScript;
- StartScript(perRequestDataPtr, scriptPtr);
- break;
- case noErr: /* Successful completion */
- /*
- * But wait, there's more -- the memory test will succeed after each
- * "chunk" of DMA -- we catch this here and re-direct (to use a legal
- * term) the secondary interrupt to the Software Task.
- */
- if (REQUEST.scriptSelector == kSCSITestMemoryScript
- && IOPB.ioActCount < IOPB.ioReqCount)
- goto needAnotherPrepareMemory;
- /* Else, continue at I/O completion */
- default:
- /*
- * Note that we use an atomic memory sequence to retrieve the old value of
- * the parameter block, then set it to NULL. This prevents two asynchronous
- * routines from calling IOCommandIsComplete on the same parameter block.
- * Ignoring atomic considerations, the while/if sequence is equivalent to
- * pb = REQUEST.pb;
- * if (pb != NULL) {
- * REQUEST.pb = NULL;
- * ... IOCommandIsComplete(...);
- * }
- */
- #if 1 && USE_LOG_LIBRARY
- WriteLogEntry(GLOBAL.logRecordPtr, 'SecC',
- LogFormat4(
- kLogFormatSigned, kLogFormatAddress,
- kLogFormatAddress, kLogFormatString
- ),
- (signed long) ioResult, REQUEST.pb,
- (((unsigned long) SCRIPT.commandCompleteByte) << 16) | SCRIPT.statusByte,
- "\pI/O complete"
- );
- #endif
- /*
- * Watch out for re-entrancy problems here. Once we call IOCommandIsComplete,
- * we must not continue the while loop as the REQUEST and REQUEST.pb may be
- * re-used because of an asynchronous request started from the completion
- * routine.
- *
- * Since we're done with this I/O request, checkpoint the per-request
- * table, but don't release system resources.
- *
- * Note: status values are not passed back to the caller -- ioResult has
- * the final operation status.
- */
- status = CheckpointIO(
- REQUEST.perRequestIOTable.preparationID,
- kMoreIOTransfers
- );
- CheckStatus(status, "\pCheckpointIO perRequest table");
- LogString("\pAfter checkpoint perRequest table");
- while ((pb = REQUEST.pb) != NULL) {
- if (IOPB.ioResult != kIOBusyStatus) {
- /*
- * We can be re-entered after KillIO. This needs improvement.
- */
- LogStatusString(IOPB.ioResult, "\pCompletion re-entered!!!");
- break;
- }
- if (CompareAndSwap((UInt32) pb, NULL, (UInt32 *) &REQUEST.pb)) {
- CancelWatchdogTimer(perRequestDataPtr);
- LogString("\pAfter I/O complete cancel watchdog");
- if (REQUEST.scriptSelector == kSCSICommandScript) {
- scsiParamPtr = (NCRSCSIParamPtr) IOPB.ioMisc;
- SCSI.statusByte = SCRIPT.statusByte;
- SCSI.messageByte = SCRIPT.commandCompleteByte;
- if (ioResult == noErr && SCSI.statusByte != kScsiCommandStatusGood) {
- ioResult = scsiNonZeroStatus;
- LogHex(SCSI.statusByte, "\pNon-zero ioResult");
- }
- }
- /*
- * Release the user I/O request physical memory allocations.
- */
- CheckpointIOTable(&REQUEST.scsiIOTable);
- LogString("\pAfter Checkpoint user data");
- /*
- * Here's where we should checkpoint the PerRequest record. Do not
- * touch pb after calling IOCommandIsComplete as it may be use
- * again. This means that we must break out of the while loop.
- */
- status = IOCommandIsComplete(REQUEST.ioCommandID, ioResult);
- LogStatusString(status, "\pAfter IOCommandIsComplete");
- break;
- }
- }
- break;
- }
- LogStatusString((OSErr) p2, "\pSecondary Handler Exit");
- return (noErr);
- #undef SCSI
- #undef PB
- }
-
- void
- StoreDMAParameters(
- PerRequestDataPtr perRequestDataPtr
- )
- {
- UInt32 ioBufferPhysAddress;
- UInt32 callerPhysAddress;
- #define PB (*((IOParam *) REQUEST.pb))
- #define SCSI (* ((NCRSCSIParamPtr) PB.ioMisc))
-
- Trace(StoreDMAParameters);
- if (REQUEST.dmaLengthPrepared == 0 || SCSI.driverAction == 0) {
- WriteByte(CTEST0, 0);
- SCRIPT.dataTable.address = 0xFFFFFFFFL;
- SCRIPT.dataTable.byteCount = 0;
- //** LogString("\pStoreDMAParameters: nothing prepared");
- }
- else if (REQUEST.scriptSelector == kSCSITestMemoryScript) {
- ioBufferPhysAddress = EndianSwap32Bit((UInt32)
- REQUEST.scsiIOTable.physicalMapping[REQUEST.physicalMapIndex]
- );
- callerPhysAddress = EndianSwap32Bit(
- ((UInt32) SCSI.memTestPhysAddress) + PB.ioActCount
- );
- REQUEST.memoryMoveScript[0] =
- EndianSwap32Bit(0xC0000000 | REQUEST.dmaLengthPrepared);
- switch (SCSI.driverAction) {
- case kNCRDriverInputAllowed: /* PBRead (physAddress->ioBuffer) */
- REQUEST.memoryMoveScript[1] = callerPhysAddress;
- REQUEST.memoryMoveScript[2] = ioBufferPhysAddress;
- break;
- case kNCRDriverOutputAllowed: /* PBWrite (ioBuffer->physAddress) */
- REQUEST.memoryMoveScript[1] = ioBufferPhysAddress;
- REQUEST.memoryMoveScript[2] = callerPhysAddress;
- break;
- }
- PB.ioActCount += REQUEST.dmaLengthPrepared;
- WriteByte(DMODE, SCSI.memTestBurstLength);
- #if USE_LOG_LIBRARY
- WriteLogEntry(GLOBAL.logRecordPtr, 'GoIo',
- LogFormat4(kLogFormatUnsigned, kLogFormatAddress, kLogFormatAddress, kLogFormatString),
- REQUEST.dmaLengthPrepared,
- REQUEST.dmaLengthPrepared,
- SCSI.memTestBurstLength,
- "\pLen Len Burst"
- );
- #endif
- }
- else { /* SCSI command */
- WriteByte(CTEST0, SCSI.driverAction);
- SCRIPT.dataTable.address = EndianSwap32Bit((UInt32)
- REQUEST.scsiIOTable.physicalMapping[REQUEST.physicalMapIndex]
- );
- SCRIPT.dataTable.byteCount = EndianSwap32Bit(REQUEST.dmaLengthPrepared);
- /*
- * Increment the actual transfer count -- this is not quite correct,
- * as we haven't actually transferred anything yet. It would be better
- * to increment the count using the actual DMA values, but this would
- * require an extra interrupt or some other fiddling in the script.
- */
- PB.ioActCount += REQUEST.dmaLengthPrepared;
- #if 1 && LOG_LIBRARY
- WriteLogEntry(GLOBAL.logRecordPtr, 'GoIO',
- LogFormat5(kLogFormatUnsigned, kLogFormatUnsigned, kLogFormatAddress,
- kLogFormatUnsigned, kLogFormatString),
- (UInt32) REQUEST.dmaLengthPrepared,
- (UInt32) SCSI.driverAction,
- EndianSwap32Bit(REQUEST.scriptData.dataTable.address),
- EndianSwap32Bit(REQUEST.scriptData.dataTable.byteCount),
- "\pcount act, addr, count"
- );
- #endif
- }
- /*
- * We just stored something into the perRequest table that the NCR chip
- * will read. Since we can be called from primary interrupt, we can't
- * call CheckpointIO. SynchronizeIO will do what we need, or so I hope.
- */
- SynchronizeIO();
- #undef PB
- #undef SCSI
- }
-
- /*
- * This function starts, or re-starts, the NCR card. The parameter is the script
- * physical address. To restart the script, use the value in the DSP register.
- */
- void
- StartScript(
- register PerRequestDataPtr perRequestDataPtr,
- UInt32 scriptPtr
- )
- {
- OSErr status;
- UInt8 dmode; /* 0x38 (for manual restart) */
- UInt8 dcntl; /* 0x3B (for manual restart) */
-
- //* Trace(StartScript);
- /*
- * Before calling this routine, the driver has messed with the per-request
- * record. Checkpoint it to ensure that the cacheing is coherent.
- */
- status = CheckpointIO(
- REQUEST.perRequestIOTable.preparationID,
- kNextIOIsInput | kNextIOIsOutput
- );
- CheckStatus(status, "\pCheckpoint perRequest restart");
- /*
- * The NCR chip is unhappy if we try to set these bits inside a script.
- *
- * Enable DMA interrupts (register 0x39 0xB9):
- * bit6 Master Data Parity Error
- * bit5 Bus Fault
- * bit4 Aborted
- * bit3 Single-step interrupt.
- * bit2 Script interrupt
- * bit0 Illegal script instruction
- */
- WriteByte(DIEN, bit6 | bit5 | bit4 | bit3 | bit2 | bit0);
- /*
- * SCSI interrupts (register 0x40 0xC0)
- * bit7 Phase mismatch
- * bit3 SCSI gross error
- * bit2 Unexpected disconnect
- * bit1 Bus reset (from an external device)
- * bit0 Parity
- * Do not interrupt on function complete, selected, or reselected.
- */
- WriteByte(SIEN0, bit7 | bit3 | bit2 | bit1 | bit0);
- /*
- * SCSI interrupts (register 0x41 0xC1)
- * bit2 Selection timeout
- * bit1 General purpose timer (used only for bus reset)
- */
- WriteByte(SIEN1, bit2 | bit1);
- /*
- * Restart the script - it will continue by jumping to one of the loops.
- * -- at least, this is the theory
- */
- LogString("\pHere we go");
- LogMemory((void *) scriptPtr, 0x20);
- WriteLong(DSP, scriptPtr);
- SynchronizeIO(); /* eieio */
- if (SINGLE_STEP) {
- /*
- * If manual-start is turned on, or we are in "single-step" mode,
- * we must restart the script by setting dcntl bit2.
- */
- dmode = ReadByte(DMODE); /* Has manual start bit */
- dcntl = ReadByte(DCNTL); /* Has single step bit */
- if ((dmode & bit0) != 0 || (dcntl & bit4) != 0) {
- WriteByte(DCNTL, dcntl | bit2);
- SynchronizeIO(); /* eieio */
- }
- }
- LogHex(scriptPtr - ((UInt32) REQUEST.scriptBaseAddress), "\pStartScript");
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * StartWatchdogTimeout
- *
- * Start an independent timer that will let us abort a run-away NCR SCSI operation.
- * We ignore any errors.
- *
- * This must be called from Secondary Interrupt Level for now.
- */
- void
- StartWatchdogTimeout(
- register PerRequestDataPtr perRequestDataPtr
- )
- {
- OSStatus osStatus;
- AbsoluteTime completionTime;
- NCRSCSIParamPtr scsiParamPtr;
- volatile AbsoluteTime now;
- #define SCSI (*scsiParamPtr)
-
- Trace(StartWatchdogTimeout);
- scsiParamPtr = (NCRSCSIParamPtr) (REQUEST.pb)->ioParam.ioMisc;
- if (REQUEST.watchdogTimeout == kNoSCSITimeout
- || REQUEST.watchdogTimeout == durationForever)
- REQUEST.timerID = kInvalidID;
- else {
- now = UpTime();
- completionTime = AddDurationToAbsolute(REQUEST.watchdogTimeout, now);
- osStatus = SetInterruptTimer(
- &completionTime,
- WatchdogTimerCompletion,
- perRequestDataPtr,
- &REQUEST.timerID
- );
- CheckStatus(osStatus, "\pSetInterruptTimer");
- if (osStatus != noErr)
- REQUEST.timerID = kInvalidID;
- }
- #undef SCSI
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * WatchdogTimerCompletion is a timer completion routine that stops a runaway
- * script.
- */
- OSStatus
- WatchdogTimerCompletion(
- void *p1, /* PerRequestDataPtr */
- void *p2 /* Current program counter -- unused */
- )
- {
- UInt8 istat;
- register PerRequestDataPtr perRequestDataPtr;
-
- Trace(WatchdogTimerCompletion);
- UNUSED(p2); /* Current program counter -- unused */
- perRequestDataPtr = (PerRequestDataPtr) p1;
- #if 1 && USE_LOG_LIBRARY
- WriteLogEntry(GLOBAL.logRecordPtr, 'Tout',
- LogFormat5(kLogFormatAddress, kLogFormatAddress,
- kLogFormatAddress, kLogFormatAddress, kLogFormatString),
- p1, p2, REQUEST.timerID, REQUEST.pb,
- "\pp1, p2, id, pb @ TimerComplete"
- );
- #endif
- if (REQUEST.pb != NULL) {
- LogString("\pI/O still running");
- /*
- * The timer fired but I/O is still running. Stop the ckip. Umm, this
- * doesn't really work: we can finish the Macintosh side, but but
- * the SCSI bus will be stuck until we force bus-reset. I don't know
- * the right solution to this problem -- it might require cooperation
- * from the application using this driver. In any case, it is specific
- * to the particular hardware device.
- */
- istat = ReadByte(ISTAT);
- WriteByte(ISTAT, istat | 0x80); /* Abort NCR Chip */
- istat = ReadByte(ISTAT);
- }
- return (noErr);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * Cancel a pending timer. This must be called from SecondaryInterrupt level.
- * An atomic sequence is used to prevent the timer from being cancelled twice.
- */
- void
- CancelWatchdogTimer(
- PerRequestDataPtr perRequestDataPtr
- )
- {
- TimerID timerID;
- AbsoluteTime timeRemaining;
- OSStatus osStatus;
-
- Trace(CancelWatchdogTimer);
- while ((timerID = REQUEST.timerID) != kInvalidID) {
- if (CompareAndSwap(
- (UInt32) timerID, kInvalidID, (UInt32 *) &REQUEST.timerID)) {
- osStatus = CancelTimer(timerID, &timeRemaining);
- CheckStatus(osStatus, "\pCancelTimer");
- #if 0 && LOG_LIBRARY
- {
- Nanoseconds nanosecondsRemaining;
-
- nanosecondsRemaining = AbsoluteToNanoseconds(timeRemaining);
- LogDecimal(nanosecondsRemaining.lo, "\pAfter CancelTimer");
- }
- #endif
- break;
- }
- }
- }
-
- #if USE_LOG_LIBRARY
- void
- LogScriptInterrupt(
- register PerRequestDataPtr perRequestDataPtr
- )
- {
- Str255 work;
- const StringPtr gBusPhase[] = { /* In sbcl and socl */
- "\p DATO", "\p DATI", "\p CMD", "\p STS",
- "\p ResO", "\p ResI", "\p MSGO", "\p MSGI"
- };
-
- //** Trace(LogScriptInterrupt);
- work[0] = 0;
- AppendHexLeadingZeros(work, SHADOW.scriptPCOffset, 3);
- AppendChar(work, ' ');
- AppendHexLeadingZeros(work, SHADOW.istat, 2); /* 14 Interrupt status */
- AppendChar(work, ' ');
- AppendHexLeadingZeros(work, SHADOW.sbcl, 2); /* 0B Active bus status */
- AppendChar(work, ' ');
- AppendHexLeadingZeros(work, SHADOW.dstat, 2); /* 0C DMA status */
- AppendChar(work, ' ');
- AppendHexLeadingZeros(work, SHADOW.sist0, 2); /* 42 SCSI interrupt stat */
- AppendChar(work, ' ');
- AppendHexLeadingZeros(work, SHADOW.sist1, 2); /* 43 SCSI interrupt stat */
- if ((SHADOW.sbcl & bit5) == 0)
- PStrCat(work, "\p ~BSY");
- else {
- if ((SHADOW.sbcl & bit7) != 0)
- PStrCat(work, "\p REQ");
- if ((SHADOW.sbcl & bit6) != 0)
- PStrCat(work, "\p ACK");
- PStrCat(work, gBusPhase[SHADOW.sbcl & 0x07]);
- }
- WriteLogEntry(GLOBAL.logRecordPtr, ' ISR', LogStringFormat, work);
- }
-
- /*
- * Dump the entire register set.
- */
- void
- DumpRegisters(
- register PerRequestDataPtr perRequestDataPtr
- )
- {
- int i;
- Str255 work;
-
- //** Trace(DumpRegisters);
- #define ncrRegisters ((UInt32 *) GLOBAL.pciCardBaseAddress)
- for (i = kRegisterBase; i < kIORegisterMax; i += 16) {
- work[0] = 0;
- AppendHexLeadingZeros(work, i, 2);
- WriteLogEntry(GLOBAL.logRecordPtr, 'Dump',
- LogFormat5(kLogFormatAddress, kLogFormatAddress,
- kLogFormatAddress, kLogFormatAddress, kLogFormatString),
- ncrRegisters[(i / sizeof (UInt32)) + 0],
- ncrRegisters[(i / sizeof (UInt32)) + 1],
- ncrRegisters[(i / sizeof (UInt32)) + 2],
- ncrRegisters[(i / sizeof (UInt32)) + 3],
- work
- );
- }
- WriteLogEntry(GLOBAL.logRecordPtr, 'Dump',
- LogFormat3(kLogFormatAddress, kLogFormatAddress, kLogFormatString),
- SCRIPT.deviceIDTable.byteCount, SCRIPT.deviceIDTable.address, "\pTarget ID"
- );
- WriteLogEntry(GLOBAL.logRecordPtr, 'Dump',
- LogFormat3(kLogFormatAddress, kLogFormatAddress, kLogFormatString),
- SCRIPT.idMsgTable.byteCount, SCRIPT.idMsgTable.address, "\pID MSG"
- );
- WriteLogEntry(GLOBAL.logRecordPtr, 'Dump',
- LogFormat3(kLogFormatAddress, kLogFormatAddress, kLogFormatString),
- SCRIPT.commandTable.byteCount, SCRIPT.commandTable.address, "\pCommand"
- );
- WriteLogEntry(GLOBAL.logRecordPtr, 'Dump',
- LogFormat3(kLogFormatAddress, kLogFormatAddress, kLogFormatString),
- SCRIPT.dataTable.byteCount, SCRIPT.dataTable.address, "\pData"
- );
- }
- #endif
-
-